﻿/**
 *	@author	Valentyn Derkach
 *
 *	2008.05.11
 *	- added toggle property
 *	- fixed mouse up handling
 */

package com.bizmlib.ui.composite
{
	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.utils.Dictionary;
	import flash.utils.setTimeout;
	import flash.utils.clearTimeout;
	
	import com.bizmlib.utils.boolean.BooleanHandling;
	
	
	public class UICompositionItem extends UICompositeItem implements IUICompositionItem
	{
		public static var EVENT_PRESS:String = "press";
		public static var EVENT_RELEASE:String = "release";
		
		protected static const STATE_CLEAN:Number = 0;
		protected static const STATE_RUNNING:Number = 1;
		
		protected var area:InteractiveObject;
		
		//protected var linkageIDArea:String = "UICompositionItemArea";
		protected var areaClass:Class = InteractiveObject;
		protected var useMouseUpAndDownEvents:Boolean = true;
		
		private var __toggle:Boolean;
		
		protected var stateRun:Number;
		protected var __dataProvider:Object;
		protected var __index:Number;
		protected var isShown:Boolean;
		protected var isPressed:Boolean;
		protected var isRollOver:Boolean;
		protected var __isEnabled:Boolean;
		protected var lastDrawState:Object;
		protected var timeoutIDShowHide:Number;
		
		
		public function UICompositionItem()
		{
			super();
			
			//trace("UICompositionItem(" + arguments + ")");
			
			/**
			if(toggle==undefined || toggle==null)
			{
				toggle = UIComposition.TOGGLE_VALUE_DEFAULT;
			}
			/**/
			
			createChildren();
		}
		
		
		public function set dataProvider(value:Object):void
		{
			if(stateRun!=STATE_CLEAN)
			{
				beforeUpdate(dataProvider);
			}
			
			__dataProvider = value;
			
			if(stateRun!=STATE_CLEAN)
			{
				afterUpdate();
			}
		}
		public function get dataProvider():Object
		{
			return __dataProvider;
		}
		
		public function set index(value:Number):void
		{
			__index = value;
			afterIndexUpdate();
		}
		public function get index():Number
		{
			return __index;
		}
		
		public function set toggle(value:Boolean):void
		{
			//trace(this + ".toggle (set) " + value);
			
			__toggle = value;
		}
		public function get toggle():Boolean
		{
			return __toggle;
		}
		public function set isEnabled(value:Boolean):void
		{
			//trace(this + ".isEnabled (set) " + arguments);
			
			if(value===isEnabled)
			{
				return;
			}
			__isEnabled = value;
			if(lastDrawState)
			{
				lastDrawState.isPressed = undefined;
				lastDrawState.isRollOver = undefined;
			}
			draw();
		}
		public function get isEnabled():Boolean
		{
			return __isEnabled;
		}
		
		
		/**
		 * Currently is only invoked outside from a UIComposition. In principle this method can be invoked from any part and should be handled correctly by a UIComposition.
		 */
		public function release(noEvent:Boolean = false):void
		{
			//trace(this + ".release(" + arguments + ")");
			
			isPressed = false;
			draw();
			
			if(!(noEvent || false))
			{
				//dispatchEvent(new UICompositionItemEvent(UICompositionItemEvent.RELEASE));
				dispatchUICompositionItemEvent(UICompositionItemEvent.RELEASE);
			}
		}
		public function press(noEvent:Boolean = false):void
		{
			//trace(this + ".press(" + arguments + ")");
			
			isPressed = true;
			isRollOver = false;
			draw();
			
			if(!(noEvent || false))
			{
				//dispatchEvent(new UICompositionItemEvent(UICompositionItemEvent.PRESS));
				dispatchUICompositionItemEvent(UICompositionItemEvent.PRESS);
			}
		}
		public function remove():void
		{
			//trace(this + ".remove(" + arguments + ")");
			
			stateRun = STATE_CLEAN;
			
			dataProvider = null;
			
			cleanClickArea();
			
			areaClass = null;
			
			clearShowHideTimeout();
			
			lastDrawState = null;
			
			if(parent)
			{
				parent.removeChild(this);
			}
			
			afterRemove();
		}
		
		
		override protected function init():void
		{
			//trace(this + ".init(" + arguments + ")");
			
			stateRun = STATE_RUNNING;
			
			super.init();
			
			isShown = false;
			isPressed = false;
			isRollOver = false;
			__isEnabled = true;
			
			timeoutIDShowHide = -1;
			
			addEventListener(Event.REMOVED_FROM_STAGE, removedFromStage);
			addEventListener(Event.ADDED_TO_STAGE, addedToStage);
			
			afterInit();
			
			lastDrawState = new Object();
			updateDrawState(isShown, isPressed, isRollOver, isEnabled);
		}
		protected function afterInit():void
		{
		}
		protected function createChildren():void
		{
			initClickArea();			
		}
		protected function initClickArea(shouldCreate:Boolean = true):void
		{
			//trace(this + ".initClickArea(" + arguments + ")");
			
			if(shouldCreate)
			{
				if(areaClass)
				{
					area = (new areaClass()) as InteractiveObject;
					this.addChild(area);
				}
			}
			
			//trace("area=" + area);
			if(area)
			{
				area.addEventListener(MouseEvent.ROLL_OVER, rollOver);
				area.addEventListener(MouseEvent.ROLL_OUT, rollOut);
				area.addEventListener(MouseEvent.CLICK, clickEventHandling);
				if(useMouseUpAndDownEvents)
				{
					area.addEventListener(MouseEvent.MOUSE_DOWN, pressEventHandling);
					//area.addEventListener(MouseEvent.MOUSE_UP, releaseEventHandling);
				}
				else
				{
					area.addEventListener(MouseEvent.CLICK, pressEventHandling, false, 2);
					area.addEventListener(MouseEvent.CLICK, releaseEventHandling, false, 1);
				}
			}
		}
		protected function cleanClickArea():void
		{
			if(area)
			{
				area.removeEventListener(MouseEvent.ROLL_OVER, rollOver);
				area.removeEventListener(MouseEvent.ROLL_OUT, rollOut);
				area.removeEventListener(MouseEvent.CLICK, clickEventHandling);
				area.removeEventListener(MouseEvent.MOUSE_DOWN, pressEventHandling);
				area.removeEventListener(MouseEvent.CLICK, releaseEventHandling);
				
				var areaAsDisplayObject:DisplayObject = area as DisplayObject;
				if(areaAsDisplayObject.parent)
				{
					areaAsDisplayObject.parent.removeChild(areaAsDisplayObject);
				}
				areaAsDisplayObject = null;
			}
		}
		protected function clickEventHandling(event:MouseEvent):void
		{
			//trace(this + ".clickEventHandling(" + arguments + ")");
			
			if(toggle)
			{
				press();
			}
		}
		protected function pressEventHandling(event:MouseEvent):void
		{
			//trace(this + ".pressEventHandling(" + arguments + ")");
			
			if(!toggle)
			{
				press();
				
				stage.addEventListener(MouseEvent.MOUSE_UP, releaseEventHandling);
			}
		}
		protected function releaseEventHandling(event:MouseEvent):void
		{
			//trace(this + ".releaseEventHandling(" + arguments + ")");
			
			if(!toggle)
			{
				release();
			}
			
			//if(area.hitTestPoint(event.stageX, event.stageY))
			if(isRollOver)
			{
				rollOver();
			}
			else
			{
				rollOut();
			}
			
			if(stage && useMouseUpAndDownEvents)
			{
				stage.removeEventListener(MouseEvent.MOUSE_UP, releaseEventHandling);
			}
		}
		
		protected function beforeUpdate(oldDataProvider:Object):void
		{
		}
		protected function afterUpdate():void
		{
		}
		
		protected function afterIndexUpdate():void
		{
		}
		
		protected function clearShowHideTimeout():void
		{
			clearTimeout(timeoutIDShowHide);
			timeoutIDShowHide = -1;
		}
		
		override protected function showStart():void
		{
			//trace(this + ".showStart(" + arguments + ")");
			
			isShown = true;
			setShowCompleteDelay();
			draw();
		}
		/**
		 * Sets a delay for a showComplete() call. Invoked froma showStart before a drawShow method is invoked. You can disable setting
		 * of a delay by passing a negative delay parameter. In that case you should invoke showComplete method yourself.
		 * @param	delay	a delay in milliseconds for a showComplete() call
		 */
		protected function setShowCompleteDelay(delay:Number = 0):void
		{
			if(delay == Number.POSITIVE_INFINITY)
			{
				return;
			}
			
			clearShowHideTimeout();
			if(delay<0)
			{
				return;
			}
			timeoutIDShowHide = setTimeout(showComplete, delay || 0);
		}
		
		override protected function hideStart():void
		{
			//trace(this + ".hideStart(" + arguments + ")");
			
			isShown = false;
			setHideCompleteDelay();
			draw();
		}
		protected function setHideCompleteDelay(delay:Number = 0):void
		{
			//trace(this + ".setHideCompleteDelay(" + arguments + ")");
			
			if(delay == Number.POSITIVE_INFINITY)
			{
				return;
			}
			
			clearShowHideTimeout();
			if(delay<0)
			{
				return;
			}
			timeoutIDShowHide = setTimeout(hideComplete, delay || 0);
		}
		
		protected function rollOver(event:MouseEvent = null):void
		{
			//trace(this + ".rollOver(" + arguments + ")");
			//trace(event.target + "\t" + event.currentTarget);
			//trace(area.mouseEnabled);
			
			isRollOver = true;
			draw();
		}
		
		protected function rollOut(event:MouseEvent = null):void
		{
			//trace(this + ".rollOut(" + arguments + ")");
			
			isRollOver = false;
			draw();
		}
		
		
		protected function updateClickAreaState():void
		{
			//trace(this + ".updateClickAreaState(" + arguments + ")");
			//trace(new Error().getStackTrace());
			//trace("isPressed=" + isPressed + "\tisShown=" + isShown + "\tisEnabled=" + isEnabled);
			
			if(area)
			{
				//trace("area.mouseEnabled=" + area.mouseEnabled);
				//trace("getAreaMouseEnabledValue()=" + getAreaMouseEnabledValue());
				//area.mouseEnabled = ((isPressed && toggle) || !isShown || !isEnabled) ? false : true;
				area.mouseEnabled = getAreaMouseEnabledValue();
				//trace("area.mouseEnabled=" + area.mouseEnabled);
				if(!area.mouseEnabled)
				{
					if(stage)
					{
						stage.removeEventListener(MouseEvent.MOUSE_UP, releaseEventHandling);
					}
				}
			}
		}
		protected function getAreaMouseEnabledValue():Boolean
		{
			return ((isPressed && toggle) || !isShown || !isEnabled) ? false : true;
		}
		protected function updateDrawState(isShown:Boolean = false, isPressed:Boolean = false, isRollOver:Boolean = false, isEnabled:Boolean = true):void
		//protected function updateDrawState(isShown:Boolean = undefined, isPressed:Boolean = undefined, isRollOver:Boolean = undefined):void
		{
			//trace(this + ".updateDrawState(" + arguments + ")");
			
			/**
			if(isShown==undefined || isPressed==undefined || isRollOver==undefined)
			{
				throw new Error("Error in " + this + ": updateDrawState got undefined in the parameters - " + arguments);
				return;
			}
			/**/
			if(lastDrawState)
			{
				lastDrawState.isShown = isShown;
				lastDrawState.isPressed = isPressed;
				lastDrawState.isRollOver = isRollOver;
				lastDrawState.isEnabled = isEnabled;
			}
		}
		//protected function draw(isShown:Boolean = undefined, isPressed:Boolean = undefined, isRollOver:Boolean = undefined, doNotUpdate:Boolean = false):void
		protected function draw(parameters:Dictionary = undefined, doNotUpdate:Boolean = false):void
		{
			//trace(this + ".draw(" + arguments + ")");
			//trace("isShown=" + isShown + "\tisPressed=" + isPressed + "\tisRollOver=" + isRollOver + "\tisEnabled=" + isEnabled);
			
			parameters = drawParametersInit(parameters);
			
			//trace(1);
			//trace("isShown=" + parameters["isShown"] + "\tisPressed=" + parameters["isPressed"] + "\tisRollOver=" + parameters["isRollOver"] + "\tdoNotUpdate=" + doNotUpdate);
			//trace(lastDrawState.isShown + "\t" + lastDrawState.isPressed + "\t" + lastDrawState.isRollOver);
			doNotUpdate = doNotUpdate || false;
			var parametersInitialCopy:Dictionary;
			if(!doNotUpdate)
			{
				updateClickAreaState();
				
				parametersInitialCopy = new Dictionary(true);
				for(var parameterName:String in parameters)
				{
					parametersInitialCopy[parameterName] = parameters[parameterName];
				}
			}
			
			drawStatesCheckup(parameters);
			
			if(!doNotUpdate)
			{
				//trace(2);
				//trace("isShown=" + parametersInitialCopy["isShown"] + "\tisPressed=" + parametersInitialCopy["isPressed"] + "\tisRollOver=" + parametersInitialCopy["isRollOver"] + "\tdoNotUpdate=" + doNotUpdate);
				//trace(lastDrawState.isShown + "\t" + lastDrawState.isPressed + "\t" + lastDrawState.isRollOver);
				updateDrawState(
					parametersInitialCopy["isShown"] as Boolean,
					parametersInitialCopy["isPressed"] as Boolean,
					parametersInitialCopy["isRollOver"] as Boolean,
					parametersInitialCopy["isEnabled"] as Boolean
				);
				//updateDrawState(isShown, isPressed, isRollOver);
			}
		}
		protected function drawParametersInit(parameters:Dictionary = undefined):Dictionary
		{
			parameters = parameters || new Dictionary(true);
			if(parameters["isShown"]==undefined && parameters["isPressed"]==undefined && parameters["isRollOver"]==undefined && parameters["isEnabled"]==undefined)
			{
				parameters["isShown"] = this.isShown;
				parameters["isPressed"] = this.isPressed;
				parameters["isRollOver"] = this.isRollOver;
				parameters["isEnabled"] = this.isEnabled;
			}
			
			return parameters;
		}
		private function drawStatesCheckup(parameters:Dictionary):void
		{
			if(!lastDrawState)
			{
				return;
			}
			if(parameters["isShown"]!=undefined)
			{
				if(lastDrawState.isShown!=parameters["isShown"])
				{
					if(parameters["isShown"])
					{
						drawShow();
					}
					else
					{
						drawHide();
					}
				}
				//else
				if(true)
				{
					/**
					 * This line catches those exeptional situations when item was released/pressed while hiding/showing.
					 */
					parameters["isShown"] = undefined;
					lastDrawState.isEnabled = undefined;
					draw(parameters, true);
					//draw(undefined, isPressed, isRollOver, true);
				}
			}
			/**/
			else if(parameters["isEnabled"]!=undefined)
			{
				if(lastDrawState.isEnabled!=parameters["isEnabled"])
				{
					if(parameters["isEnabled"])
					{
						drawEnable();
						parameters["isEnabled"] = undefined;
						draw(parameters, true);
					}
					else
					{
						drawDisable();
					}
				}
				else if(parameters["isEnabled"])
				{
					parameters["isEnabled"] = undefined;
					draw(parameters, true);
				}
			}
			/**/
			else if(parameters["isPressed"]!=undefined)
			//else if(lastDrawState.isPressed!=isPressed)
			{
				//trace(2);
				if(lastDrawState.isPressed!=parameters["isPressed"])
				{
					if(parameters["isPressed"])
					{
						drawPress(parameters["isShown"]==undefined);
					}
					else
					{
						drawRollOut(parameters["isShown"]==undefined);
					}
				}
				else
				{
					parameters["isShown"] = undefined;
					parameters["isPressed"] = undefined;
					draw(parameters, true);
					//draw(undefined, undefined, isRollOver, true);
				}
			}
			else if(parameters["isRollOver"]!=lastDrawState.isRollOver)
			{
				if(parameters["isRollOver"])
				{
					drawRollOver(parameters["isShown"]==undefined);
				}
				else
				{
					drawRollOut(parameters["isShown"]==undefined);
				}
			}
		}
		protected function drawShow():void
		{
		}
		protected function drawHide():void
		{
		}
		protected function drawEnable():void
		{
			//trace(this + ".drawEnable(" + arguments + ")");
		}
		protected function drawDisable():void
		{
			//trace(this + ".drawDisable(" + arguments + ")");
		}
		protected function drawPress(exceptionalCall:Boolean):void
		{
		}
		protected function drawRollOver(exceptionalCall:Boolean):void
		{
		}
		protected function drawRollOut(exceptionalCall:Boolean):void
		{
		}
		
		
		protected function dispatchUICompositionItemEvent(eventName:String):void
		{
			dispatchEvent(new UICompositionItemEvent(eventName, this));
		}
		
		
		private function addedToStage(event:Event):void
		{
			afterAddedToStage();
		}
		protected function afterAddedToStage():void
		{
			
		}
		private function removedFromStage(event:Event):void
		{
			stage.removeEventListener(MouseEvent.MOUSE_UP, releaseEventHandling);
			
			afterRemovedFromStage();
		}
		protected function afterRemovedFromStage():void
		{
		}
		
		protected function afterRemove():void
		{
			//...
		}
		
		
		override public function toString():String
		{
			return "[UICompositionItem]";
		}
	}
}